package com.imgpro.service.Impl;

import com.google.gson.Gson;
import com.imgpro.Constant.MessageConstant;
import com.imgpro.Context.BaseContext;
import com.imgpro.Exception.ServerBusyException;
import com.imgpro.Utils.AliyunOSSOperator;
import com.imgpro.constants.RErrorConstant;
import com.imgpro.constants.RedisConstant;
import com.imgpro.dto.UserDTO;
import com.imgpro.service.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.awt.image.BufferedImage;
import java.util.concurrent.TimeUnit;

import net.coobird.thumbnailator.Thumbnails;
//import net.coobird.thumbnailator.geometry.Positions;

import javax.imageio.ImageIO;
import javax.security.auth.login.AccountNotFoundException;

@Service
@Slf4j
public class CommonServiceImpl implements CommonService {

    private static final int TARGET_SHORT_SIDE_PIXELS = 360;
    private static final String OUTPUT_FOLDER_NAME = "output";
    private static final String RESIZED_SUFFIX = "_small";

    @Autowired
    private AliyunOSSOperator aliyunOSSOperator;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    @Override
    public String getCurrentUUid() throws Exception {
        String userKey = BaseContext.getCurrentId();
        if(userKey == null){
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

//        userKey = RedisConstant.LOGIN_TOKEN_KEY + userKey;

        HashOperations<String, Object, Object> opsForHash = stringRedisTemplate.opsForHash();
//         查询redis中存储的token信息,作为cache
        Map<Object, Object> userMap = opsForHash.entries(RedisConstant.LOGIN_TOKEN_KEY + userKey);

        UserDTO userDTO ;

        Gson gson = new Gson();
        if(!userMap.isEmpty()){
            String json = gson.toJson(userMap);
            userDTO = gson.fromJson(json , UserDTO.class);
            log.info("从Redis缓存里面读取到了用户信息，直接返回");
            return userDTO.getUuid();
        }

        log.info("Redis中没有缓存用户信息，直接返回ThreadLocal中的UUID");
        return userKey;
    }



    @Override
    public Path doImgPro(File inputFolderPath) throws Exception {
        String projectRootPath = System.getProperty("user.dir");
        File outputfolder = new File(projectRootPath + File.separator + "output");

        if (!outputfolder.exists()) {
            outputfolder.mkdirs();
        }
//        String fileName = BaseContext.getCurrentId();

        log.info("TODO ++++++++++现在是要对这个照片进行大量的处理我抄就是一个处理++++++++++");

        long pid = ProcessHandle.current().pid();
        log.info("当前进程ID：" + pid);
        String lock = RedisConstant.LOCK_PYIMGPRO + pid;
        RLock rlock = redissonClient.getLock(lock);
//
        boolean isLocked = rlock.tryLock(RedisConstant.LOCK_TIMEOUT, RedisConstant.LEASE_TIME, TimeUnit.SECONDS);

        if(!isLocked){
            log.info("当前线程被锁住，其他人正在使用pyImgPro正在等待锁释放");
//            没有获取到锁，直接一个释放
            throw new ServerBusyException(RErrorConstant.SERVER_IS_BUSY);
        }

        try {
            Path rPath = relocateToTest(inputFolderPath, outputfolder);
            return rPath;
        } catch (Exception e) {
            throw new Exception(e);
        } finally {
            rlock.unlock();
        }
    }

    @Override
    public Path doSmallize(Path inputPath) throws Exception {

        if (inputPath == null) {
            throw new NullPointerException("Input path cannot be null.");
        }
        if (!Files.exists(inputPath)) {
            throw new NoSuchFileException("Input file does not exist: " + inputPath);
        }
        if (!Files.isRegularFile(inputPath)) {
            throw new IllegalArgumentException("Input path does not point to a regular file: " + inputPath);
        }

        // --- Prepare Output Directory ---
        String projectRootPath = System.getProperty("user.dir");
        Path outputFolderPath = Paths.get(projectRootPath, OUTPUT_FOLDER_NAME); // Use Paths for consistency

        if (!Files.exists(outputFolderPath)) {
            System.out.println("Output directory does not exist, creating: " + outputFolderPath.toAbsolutePath());
            try {
                Files.createDirectories(outputFolderPath);
            } catch (IOException e) {
                throw new IllegalStateException("Could not create output directory: " + outputFolderPath.toAbsolutePath(), e);
            }
        } else if (!Files.isDirectory(outputFolderPath)) {
            throw new IllegalStateException("Output path exists but is not a directory: " + outputFolderPath.toAbsolutePath());
        } else {
            System.out.println("Output directory found: " + outputFolderPath.toAbsolutePath());
        }


        // --- Determine Output Filename ---
        String originalFileName = inputPath.getFileName().toString();
        String baseName;
        String extension;
        int dotIndex = originalFileName.lastIndexOf('.');

        if (dotIndex > 0 && dotIndex < originalFileName.length() - 1) {
            baseName = originalFileName.substring(0, dotIndex);
            extension = originalFileName.substring(dotIndex); // Includes the dot
        } else {
            // Handle files with no extension (less common for images)
            baseName = originalFileName;
            extension = "";
        }
        // Ensure suffix is added correctly, even if original has _small already
        if (baseName.toLowerCase().endsWith(RESIZED_SUFFIX.toLowerCase())) {
            // Avoid adding suffix twice, maybe log a warning or decide on behavior
            System.out.println("Warning: Input filename already ends with suffix '" + RESIZED_SUFFIX + "'. Proceeding without adding suffix again.");
        } else {
            baseName += RESIZED_SUFFIX;
        }

        String outputFileName = baseName + extension;
        Path outputFilePath = outputFolderPath.resolve(outputFileName);

        System.out.println("Target output file path: " + outputFilePath.toAbsolutePath());


        // --- Read Original Image Dimensions ---
        BufferedImage originalImage;
        try {
            originalImage = ImageIO.read(inputPath.toFile());
            if (originalImage == null) {
                // ImageIO.read returns null for unsupported formats or invalid files
                throw new IOException("Could not read image file, possibly unsupported format or corrupted: " + inputPath);
            }
        } catch (IOException e) {
            throw new IOException("Failed to read image file: " + inputPath, e);
        }

        int originalWidth = originalImage.getWidth();
        int originalHeight = originalImage.getHeight();
        System.out.println("Original dimensions: " + originalWidth + "x" + originalHeight);


        // --- Perform Resizing with Thumbnailator ---
        try {
            System.out.println("Starting image resizing process...");

            // 1. 开始构建，只指定输入图片
            Thumbnails.Builder<BufferedImage> builder = Thumbnails.of(originalImage);

            // 2. 根据短边决定尺寸，并在此处同时指定保持宽高比
            if (originalWidth < originalHeight) {
                // 宽度是短边
                if (originalWidth > TARGET_SHORT_SIDE_PIXELS) {
                    System.out.println("宽度是短边. 调整宽度至 " + TARGET_SHORT_SIDE_PIXELS);
                    builder = builder.width(TARGET_SHORT_SIDE_PIXELS) // 先指定尺寸
                            .keepAspectRatio(true);           // 后指定保持比例
                } else {
                    System.out.println("原始宽度 (" + originalWidth + ") 已经小于等于目标 ("+ TARGET_SHORT_SIDE_PIXELS +").");
                    // 即使不缩小，也设定目标尺寸和规则
                    builder = builder.width(TARGET_SHORT_SIDE_PIXELS)
                            .keepAspectRatio(true);
                }
            } else {
                // 高度是短边或者图片是方形
                if (originalHeight > TARGET_SHORT_SIDE_PIXELS) {
                    System.out.println("高度是短边/方形. 调整高度至 " + TARGET_SHORT_SIDE_PIXELS);
                    builder = builder.height(TARGET_SHORT_SIDE_PIXELS) // 先指定尺寸
                            .keepAspectRatio(true);            // 后指定保持比例
                } else {
                    System.out.println("原始高度 (" + originalHeight + ") 已经小于等于目标 ("+ TARGET_SHORT_SIDE_PIXELS +").");
                    // 即使不缩小，也设定目标尺寸和规则
                    builder = builder.height(TARGET_SHORT_SIDE_PIXELS)
                            .keepAspectRatio(true);
                }
            }

            // 3. 现在可以添加其他与尺寸计算无关的参数
            builder = builder     // 输出格式
                    .outputQuality(0.85f);     // 输出质量

            // 4. 执行缩放并保存
            System.out.println("保存缩放后的图片到: " + outputFilePath);
            builder.toFile(outputFilePath.toFile());

            System.out.println("成功缩放并保存图片。");

            return outputFilePath;

        } catch (IOException e) {
            // ... 其他 catch 块 ...
            System.err.println("图片处理/保存过程中发生 I/O 错误: " + e.getMessage());
            try { Files.deleteIfExists(outputFilePath); } catch (IOException ignored) {} // 尝试删除可能不完整的输出文件
            throw new IOException("缩放或保存图片到 " + outputFilePath + " 失败", e);
        } catch (Exception e) {
            // ... 其他 catch 块 ...
            System.err.println("图片缩放过程中发生意外错误: " + e.getMessage());
            e.printStackTrace();
            try { Files.deleteIfExists(outputFilePath); } catch (IOException ignored) {}
            throw new IOException("图片处理过程中发生意外错误。", e);
        }

    }

    @Override
    public List<String> uploadToOSS(Path output, Path outputSmallize) {
//        将两个文件上传到阿里云
        Objects.requireNonNull(output, "原始文件路径 (output) 不能为空");
        Objects.requireNonNull(outputSmallize, "压缩文件路径 (outputSmallize) 不能为空");

        List<String> ossUrls = new ArrayList<>();
        String outputUrl = null; // 先声明变量

        try {
            // 2. 上传第一个文件 (output)
            outputUrl = uploadSingleFile(output);
            ossUrls.add(outputUrl);

            // 3. 上传第二个文件 (outputSmallize)
            String outputSmallizeUrl = uploadSingleFile(outputSmallize);
            ossUrls.add(outputSmallizeUrl);

            // 4. 返回包含两个 URL 的列表
//            log.info("成功上传两个文件到 OSS. URLs: {}", ossUrls);
            return ossUrls;

        } catch (IOException e) {
            // 如果发生 IO 异常（文件读取或上传），记录错误并重新抛出
            // 注意：如果第一个文件上传成功，但第二个失败，这里不会自动删除第一个文件。
            // 你可能需要根据业务需求添加补偿逻辑（例如删除已上传的 outputUrl）。
            log.error("上传文件到 OSS 时发生 IO 错误", e);
            // 考虑包装成自定义业务异常或保持 IOException
            throw new RuntimeException("上传文件到 OSS 失败: " + e.getMessage(), e);
        } catch (Exception e) {
            // 捕获其他可能的运行时异常（例如 OSS SDK 内部错误）
            log.error("上传文件到 OSS 时发生意外错误", e);
            throw new RuntimeException("上传文件到 OSS 时发生意外失败: " + e.getMessage(), e);
        }
    }

    @Override
    public void deleteOrigin(File folder) throws Exception{
//        String fileName = BaseContext.getCurrentId();

        if (folder == null) {
            log.error("输入的文件夹为 null");
            throw new IllegalArgumentException("文件夹参数不能为 null");
        }
        if (!folder.exists()) {
            log.error("文件夹不存在: {}", folder.getAbsolutePath());
            // 可以选择抛出异常，或者只是记录日志并返回，取决于你的需求
            // 这里选择记录日志并静默返回，因为有时“清理不存在的文件夹”可能不算错误
            log.warn("尝试删除文件的文件夹不存在，操作跳过: {}", folder.getAbsolutePath());
            return;
            // 或者抛出异常: throw new IllegalArgumentException("文件夹不存在: " + folder.getAbsolutePath());
        }
        if (!folder.isDirectory()) {
            log.error("提供的路径不是一个文件夹: {}", folder.getAbsolutePath());
            throw new IllegalArgumentException("提供的路径不是一个文件夹: " + folder.getAbsolutePath());
        }

        // 2. 获取并校验文件名前缀
        String fileNamePrefix = getCurrentUUid();
        // 使用 Objects.requireNonNull 确保 BaseContext 返回的不是 null
        // Objects.requireNonNull(fileNamePrefix, "从 BaseContext 获取的文件名前缀不能为 null");
        // 或者更友好的处理 null 或空字符串：
        if (fileNamePrefix == null || fileNamePrefix.trim().isEmpty()) {
            log.error("从 BaseContext 获取的文件名前缀为 null 或空，无法进行删除操作。");
            throw new IllegalArgumentException("无效的文件名前缀，无法删除文件。");
        }
//        log.info("准备在文件夹 '{}' 中删除以 '{}' 开头的文件...", folder.getAbsolutePath(), fileNamePrefix);


        // 3. 列出文件夹中的文件
        File[] files = folder.listFiles(); // 获取文件夹内容

        // 检查 listFiles 的结果，如果文件夹无法访问（例如权限问题），它可能返回 null
        if (files == null) {
            log.error("无法列出文件夹 '{}' 中的文件（请检查读取权限）", folder.getAbsolutePath());
            // 可以选择抛出异常或仅记录日志
            throw new RuntimeException("无法访问文件夹内容: " + folder.getAbsolutePath());
            // 或者 log.warn(...) 并 return;
        }

        // 4. 遍历并删除匹配的文件
        int deletedCount = 0;
        int failedCount = 0;
        for (File file : files) {
            // 确保处理的是文件，而不是子目录
            if (file.isFile()) {
                String currentFileName = file.getName();
                // 检查文件名是否以指定前缀开头（区分大小写）
                // 如果需要不区分大小写，可以使用: currentFileName.toLowerCase().startsWith(fileNamePrefix.toLowerCase())
                if (currentFileName.startsWith(fileNamePrefix)) {
//                    log.debug("找到匹配文件，尝试删除: {}", file.getAbsolutePath());
                    // 尝试删除文件
                    if (file.delete()) {
//                        log.info("成功删除文件: {}", file.getAbsolutePath());
                        deletedCount++;
                    } else {
                        // 删除失败（可能原因：文件被占用、权限不足等）
                        log.warn("无法删除文件: {} (可能被占用或权限不足)", file.getAbsolutePath());
                        failedCount++;
                    }
                }
            }
        }

//        log.info("文件删除操作完成。在 '{}' 中删除了 {} 个以 '{}' 开头的文件，{} 个删除失败。",
//                folder.getAbsolutePath(), deletedCount, fileNamePrefix, failedCount);

    }

    @Override
    public void deleteOutPut() throws Exception {
        String projectRootPath = System.getProperty("user.dir");
        File outputfolder = new File(projectRootPath + File.separator + "output");
        deleteOrigin(outputfolder);
    }


    private String getFileNameWithoutExtension(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return fileName;
        }
        int lastDotIndex = fileName.lastIndexOf('.');
        // 确保点不是第一个字符，并且后面有字符
        if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
            return fileName.substring(0, lastDotIndex);
        }
        return fileName; // 没有找到有效的点，返回原文件名
    }
    private String uploadSingleFile(Path filePath) throws Exception {
        String fileName = filePath.getFileName().toString();
//        log.info("准备上传文件 '{}' 到 OSS...", fileName);

        // 检查文件是否存在且是普通文件
        if (!Files.exists(filePath)) {
            log.error("文件不存在: {}", filePath.toAbsolutePath());
            throw new IOException("文件未找到: " + filePath.toAbsolutePath());
        }
        if (!Files.isRegularFile(filePath)) {
            log.error("路径不是一个普通文件: {}", filePath.toAbsolutePath());
            throw new IOException("路径不是一个文件: " + filePath.toAbsolutePath());
        }

        // 读取文件内容为字节数组
        byte[] fileBytes;
        try {
            fileBytes = Files.readAllBytes(filePath);
            log.debug("成功读取文件 '{}', 大小: {} bytes", fileName, fileBytes.length);
        } catch (IOException e) {
            log.error("读取文件 '{}' 失败", fileName, e);
            throw e; // 重新抛出读取异常
        } catch (OutOfMemoryError oom) {
            log.error("读取文件 '{}' 时内存不足！文件可能过大。", fileName, oom);
            throw new IOException("读取文件时内存不足: " + fileName, oom);
        }


        // 调用 OSS 上传方法
        // **关键：假设 aliyunOSSOperator.upload(byte[], String) 返回 String URL 并可能抛出异常**
        try {
            String url = aliyunOSSOperator.upload(fileBytes, fileName);
            if (url == null || url.trim().isEmpty()) {
                // 防御性编程：如果 OSS 操作类可能返回 null 或空字符串
                log.error("OSS 操作类为文件 '{}' 返回了无效的 URL", fileName);
                throw new IOException("OSS 上传后未能获取有效的 URL: " + fileName);
            }
//            log.info("文件 '{}' 成功上传到 OSS. URL: {}", fileName, url);
            return url;
        } catch (Exception e) { // 捕获 OSS 上传本身的 IO 异常
            log.error("调用 OSS 上传文件 '{}' 时发生 IO 异常", fileName, e);
            throw e;
        }
        // 如果 aliyunOSSOperator.upload 抛出的是非 IOException 的运行时异常，它们会被外层调用者捕获
    }

    private Path relocateToTest(File inputFolderPath, File outputfolder) throws Exception{
        // 2. 获取要查找的文件名 (无后缀)
//        TODO 以后这个文件名应该从redis里面读取
//        Thread.sleep(10000);
        String fileNameWithoutExtension = getCurrentUUid();


        // 假设这会返回类似 "myFile123" 的字符串
        Objects.requireNonNull(fileNameWithoutExtension, "File name from BaseContext cannot be null");
        if (fileNameWithoutExtension.trim().isEmpty()){
            throw new IllegalArgumentException("File name from BaseContext cannot be empty");
        }
        System.out.println("Looking for file with base name: " + fileNameWithoutExtension);


        // 3. 检查输入文件夹
        File inputFolder = new File(String.valueOf(inputFolderPath));
        if (!inputFolder.exists() || !inputFolder.isDirectory()) {
            throw new NoSuchFileException("Input folder does not exist or is not a directory: " + inputFolderPath);
        }
        System.out.println("Searching in input directory: " + inputFolder.getAbsolutePath());

//        String fileName = BaseContext.getCurrentId();
        File sourceFile = null;
        String sourceFileNameWithExtension = null; // 存储找到的文件的完整名称

        File[] files = inputFolder.listFiles(); // 获取输入文件夹中的所有文件和目录
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) { // 只处理文件，忽略子目录
                    String currentFileName = file.getName();
                    String currentFileNameWithoutExtension = getFileNameWithoutExtension(currentFileName);

                    // 比较文件名（不包括后缀），忽略大小写
                    if (fileNameWithoutExtension.equalsIgnoreCase(currentFileNameWithoutExtension)) {
                        sourceFile = file;
                        sourceFileNameWithExtension = currentFileName; // 保存完整文件名
                        System.out.println("Found matching file: " + sourceFile.getAbsolutePath());
                        break; // 找到后退出循环
                    }
                }
            }
        } else {
            throw new IOException("Could not list files in input directory (check permissions?): " + inputFolderPath);
        }

        // 5. 检查是否找到文件
        if (sourceFile == null) {
            throw new NoSuchFileException("File with base name '" + fileNameWithoutExtension + "' not found in " + inputFolderPath);
        }

        // 6. 构建目标文件路径
        Path sourcePath = sourceFile.toPath(); // 源文件路径 (NIO Path)
        Path destinationPath = outputfolder.toPath().resolve(sourceFileNameWithExtension); // 目标文件路径 (NIO Path)
//        System.out.println("Attempting to move to: " + destinationPath);


        // 7. 移动文件 (使用 NIO Files.move)
        try {
            // StandardCopyOption.REPLACE_EXISTING 表示如果目标文件已存在，则替换它
            // 如果不想替换，可以去掉这个选项，如果目标存在则会抛出 FileAlreadyExistsException
            Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("Successfully moved file to: " + destinationPath);
            log.info("TODO +++++++++++++++假设现在已经处理完了反正就是处理完了+++++++++++++++");
            return destinationPath;
        } catch (IOException e) {
            System.err.println("Error moving file: " + e.getMessage());
            // 可以选择重新抛出异常，让调用者知道操作失败
            throw new IOException("Failed to move file from " + sourcePath + " to " + destinationPath, e);
        }

//        String fileNameWithoutExtension = BaseContext.getCurrentId();


    }
}
